// 2024/1/15: 首个版本
// 2024/3/6: 支持接收超过64字节的总线数据
// 2024/7/22: 支持解析Segment协议
// 2024/7/23: SendLoadedFileData改为StartSendingLoadedFileData
// 2024/7/24: 支持Protocol.DebugPrint和Protocol.AudioFrame
// 2024/8/15: Connect输入SettingsProto
// 2024/8/19: 新增Send。支持接收AgencyOutput
// 2024/8/21: 支持接收Box。AgencyOutput区分void和空数据
// 2024/8/30: Socket通讯改为异步
// 2024/9/27: 支持StopSessionProto.RecordedSession
// 2024/10/10: 支持接收SubscriberResponse
// 2024/10/10: 修正SendSubscriberRequest并增加checkOnly参数
// 2024/11/6: 新增SendTaskCancellation
// 2024/11/27: 支持接收TaskXXXProto
// 2024/12/25: 支持接收110~118协议，支持发送SetVideoTransferProto
// 2025/1/21: 使用SocketHandler接口，支持worker连接
// 2025/5/15: 总线通道数扩展至48
// 2025/5/27: 新增SendRoundTripTime
// 2025/7/22: 新增RoundTripTest响应
// 2025/7/23: 通讯协议v2
// 2025/7/25: 支持解析SessionBusMessages和AgencyOutputs
// 2025/7/28: AgencyOutputs结构优化

using System;
using System.Text;
using System.Collections.Generic;
using System.Text.Json;
using System.Linq;
using System.Collections.Concurrent;
using System.Threading.Tasks;
using ASEva;
using ASEva.Samples;

namespace RemoteConsole
{
    class RemoteConsole(SocketHandler socket)
    {
        public async Task<bool> ConnectConsole(String consoleIP, ushort inPort, ushort outPort, SettingsProto settings)
        {
            if (socket.IsConnected()) return false;

            var masks = new Masks();
            masks.UpdateSeed(settings.MaskSeed);

            var ok = await socket.Open(consoleIP, inPort, outPort, masks);
            if (ok && !await sendProtoObject(Protocol.Settings, settings, false)) ok = false;
            return ok;
        }

        public async Task<bool> ConnectWorker(String webSocketIP, ushort port, int maskSeed)
        {
            if (socket.IsConnected()) return false;

            var masks = new Masks();
            masks.UpdateSeed(maskSeed);

            var ok = await socket.Open(webSocketIP, port, 0, masks);
            if (ok && !await sendProtoObject(Protocol.MaskSeed, new MaskSeedProto{ MaskSeed = maskSeed }, false)) ok = false;
            return ok;
        }

        public bool IsConnected()
        {
            return socket.IsConnected();
        }

        public async Task Disconnect()
        {
            if (socket.IsConnected()) await sendProtocolOnly(Protocol.Disconnect, true);

            if (sendFileDataTask != null)
            {
                shouldEnd = true;
                await sendFileDataTask;
                sendFileDataTask = null;
                shouldEnd = false;
            }
            packetsToSend.Clear();

            await socket.Close();
            segmentRestore.Reset();
        }

        public Task<bool> Send(byte[] packet, bool optional)
        {
            return socket.Send(packet, optional);
        }

        public object[] Receive()
        {
            var receivedPackets = socket.Receive().ToList();
            segmentRestore.Restore(receivedPackets);

            var objs = new List<object>();
            foreach (var binary in receivedPackets)
            {
                var protocol = (Protocol)BitConverter.ToInt32(binary.AsSpan(0, 4));
                switch (protocol)
                {
                    case Protocol.Print: // 输出PrintProto
                        objs.Add(JsonSerializer.Deserialize<PrintProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.Confirm: // 输出ConfirmProto，收到后应调用Prompt，输入o或x
                        objs.Add(JsonSerializer.Deserialize<ConfirmProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.InputNumber: // 输出InputNumberProto，收到后应调用Prompt，输入数字
                        objs.Add(JsonSerializer.Deserialize<InputNumberProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.InputString: // 输出InputStringProto，收到后应调用Prompt，输入字符串（可空）
                        objs.Add(JsonSerializer.Deserialize<InputStringProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.SingleSelect: // 输出SingleSelectProto，收到后应调用Prompt，输入序号
                        objs.Add(JsonSerializer.Deserialize<SingleSelectProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.MultiSelect: // 输出MultiSelectProto，收到后应调用Prompt，输入以逗号分割的多个序号
                        objs.Add(JsonSerializer.Deserialize<MultiSelectProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.LoadFileData: // 输出LoadFileDataProto，收到后应调用SendLoadedFileData，若取消则输入data为null
                        objs.Add(JsonSerializer.Deserialize<LoadFileDataProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.SaveFileData: // 输出SaveFileDataRequest，收到后应调用SendSaveFileDataResult，若取消则输入saved为false
                        {
                            var jsonSize = BitConverter.ToInt32(binary.AsSpan(4, 4));
                            var request = new SaveFileDataRequest();
                            request.Proto = JsonSerializer.Deserialize<SaveFileDataProto>(binary.AsSpan(8, jsonSize));
                            request.SaveFileData = binary.AsSpan(8 + jsonSize, request.Proto.DataSize).ToArray();
                            objs.Add(request);
                        }
                        break;
                    case Protocol.Box:
                        objs.Add(JsonSerializer.Deserialize<BoxProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.StartupState: // 输出StartupStateProto
                        objs.Add(JsonSerializer.Deserialize<StartupStateProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.TransferStat: // 输出TransferStatProto
                        objs.Add(JsonSerializer.Deserialize<TransferStatProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.StartSession: // 输出StartSessionProto
                        objs.Add(JsonSerializer.Deserialize<StartSessionProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.StopSession: // 输出StopSessionProto
                        if (binary.Length > 4) objs.Add(JsonSerializer.Deserialize<StopSessionProto>(binary.AsSpan(4)));
                        else objs.Add(new StopSessionProto());
                        break;
                    case Protocol.ManualTrigger: // 输出ReceivedManualTrigger
                        {
                            var trig = new ReceivedManualTrigger();
                            trig.Data = binary;
                            trig.HostPosix = BitConverter.ToUInt64(binary.AsSpan(4, 8));
                            trig.States = new bool[16];
                            for (int i = 0; i < 16; i++) trig.States[i] = binary[12 + i] != 0;
                            objs.Add(trig);
                        }
                        break;
                    case Protocol.BusMessage: // 输出ReceivedBusMessage
                        {
                            var msg = new ReceivedBusMessage();
                            msg.Data = binary;
                            msg.HostPosix = BitConverter.ToUInt64(binary.AsSpan(4, 8));
                            msg.ID = BitConverter.ToUInt32(binary.AsSpan(12, 4));
                            var msgDataLength = (int)binary[16];
                            msg.Channel = binary[17];
                            msg.Type = (BusChannelType)binary[18];
                            msg.State = (BusRawDataState)binary[19];
                            msg.ActualLength = BitConverter.ToUInt16(binary.AsSpan(20, 2));
                            if (msgDataLength == 0) msgDataLength = msg.ActualLength;
                            msg.MessageData = new byte[msgDataLength];
                            for (int i = 0; i < msgDataLength; i++) msg.MessageData[i] = binary[22 + i];
                            objs.Add(msg);
                        }
                        break;
                    case Protocol.VideoFrame: // 输出ReceivedVideoFrame
                        {
                            var frame = new ReceivedVideoFrame();
                            frame.Data = binary;
                            frame.HostPosix = BitConverter.ToUInt64(binary.AsSpan(4, 8));
                            var jpegSize = BitConverter.ToUInt32(binary.AsSpan(12, 4));
                            frame.Channel = binary[16];
                            frame.Jpeg = new byte[jpegSize];
                            for (int i = 0; i < jpegSize; i++) frame.Jpeg[i] = binary[17 + i];
                            objs.Add(frame);
                        }
                        break;
                    case Protocol.Signal: // 输出ReceivedSignals
                        {
                            var signals = new ReceivedSignals();
                            var idLength = BitConverter.ToUInt32(binary.AsSpan(4, 4));
                            var signalNum = BitConverter.ToUInt32(binary.AsSpan(8, 4));
                            var idBytes = new byte[idLength];
                            Array.Copy(binary, 12, idBytes, 0, idLength);
                            signals.SignalID = Encoding.UTF8.GetString(idBytes);
                            var signalPosix = new ulong[signalNum];
                            var signalValues = new double[signalNum];
                            var curIndex = 12 + (int)idLength;
                            for (int i = 0; i < signalNum; i++)
                            {
                                signalPosix[i] = BitConverter.ToUInt64(binary.AsSpan(curIndex, 8));
                                signalValues[i] = BitConverter.ToDouble(binary.AsSpan(curIndex + 8, 8));
                                curIndex += 16;
                            }
                            signals.HostPosix = signalPosix;
                            signals.Values = signalValues;
                            objs.Add(signals);
                        }
                        break;
                    case Protocol.GeneralSample: // 输出ReceivedGeneralSample
                        {
                            var proto = JsonSerializer.Deserialize<GeneralSampleProto>(binary.AsSpan(4));
                            var sample = new ReceivedGeneralSample();
                            sample.HostPosix = proto.HostPosix;
                            var atIndex = proto.ChannelID.LastIndexOf('@');
                            int channelIndex = 0;
                            if (atIndex > 0 && atIndex < proto.ChannelID.Length - 1 && Int32.TryParse(proto.ChannelID.AsSpan(atIndex + 1), out channelIndex))
                            {
                                sample.Protocol = proto.ChannelID.Substring(0, atIndex);
                                sample.Channel = channelIndex;
                            }
                            else
                            {
                                sample.Protocol = proto.ChannelID;
                                sample.Channel = null;
                            }
                            sample.Elements = new GeneralSampleValue[proto.Elements.Length];
                            for (int i = 0; i < proto.Elements.Length; i++)
                            {
                                if (proto.Elements[i] == null) continue;
                                double number = 0;
                                if (Double.TryParse(proto.Elements[i], out number)) sample.Elements[i] = new GeneralSampleValue(number);
                                else sample.Elements[i] = new GeneralSampleValue(proto.Elements[i]);
                            }
                            objs.Add(sample);
                        }
                        break;
                    case Protocol.PointCloud: // 输出ReceivedPointCloud
                        {
                            var pc = new ReceivedPointCloud();
                            pc.HostPosix = BitConverter.ToUInt64(binary.AsSpan(4, 8));
                            pc.PointCount = (int)BitConverter.ToUInt32(binary.AsSpan(12, 4));
                            pc.Channel = binary[16];
                            var withI = binary[17] == 1;
                            pc.Points = new short[pc.PointCount, 3];
                            if (withI) pc.Intensities = new ushort[pc.PointCount];
                            var curIndex = 18;
                            for (int i = 0; i < pc.PointCount; i++)
                            {
                                byte b0 = binary[curIndex], b1 = binary[curIndex + 1], b2 = binary[curIndex + 2], b3 = binary[curIndex + 3];
                                var xCode = (uint)b0 | (((uint)b1 & 0x0f) << 8);
                                var yCode = (((uint)b1 & 0xf0) >> 4) | ((uint)b2 << 4);
                                var zCode = b3;

                                pc.Points[i, 0] = (short)(((int)xCode - 2048) * 5);
                                pc.Points[i, 1] = (short)(((int)yCode - 2048) * 5);
                                pc.Points[i, 2] = (short)(((int)zCode - 32) * 5);

                                if (withI)
                                {
                                    var I = (float)binary[curIndex + 4] / 255;
                                    pc.Intensities[i] = (ushort)Math.Round(I * 10000);
                                    curIndex += 5;
                                }
                                else curIndex += 4;
                            }
                            objs.Add(pc);
                        }
                        break;
                    case Protocol.TransferSignalList: // 输出TransferSignalListProto
                        objs.Add(JsonSerializer.Deserialize<TransferSignalListProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.TransferSampleList: // 输出TransferSampleListProto
                        objs.Add(JsonSerializer.Deserialize<TransferSampleListProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.DebugPrint: // 输出DebugPrintProto
                        objs.Add(JsonSerializer.Deserialize<DebugPrintProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.AudioFrame: // 输出ReceivedAudioFrame
                        {
                            var frame = new ReceivedAudioFrame();
                            frame.HostPosix = BitConverter.ToUInt64(binary.AsSpan(4, 8));
                            var sampleCount = BitConverter.ToUInt32(binary.AsSpan(12, 4));
                            frame.Samples = new short[sampleCount];
                            for (int i = 0; i < sampleCount; i++) frame.Samples[i] = BitConverter.ToInt16(binary, 16 + 2 * i);
                            objs.Add(frame);
                        }
                        break;
                    case Protocol.AgencyOutput: // 输出AgencyOutput
                        {
                            var jsonSize = BitConverter.ToInt32(binary.AsSpan(4, 4));
                            var output = new AgencyOutput();
                            output.Proto = JsonSerializer.Deserialize<AgencyOutputProto>(binary.AsSpan(8, jsonSize));
                            if (output.Proto.DataSize > 0) output.OutputData = binary.AsSpan(8 + jsonSize, output.Proto.DataSize).ToArray();
                            else if (output.Proto.DataSize == 0) output.OutputData = new byte[0];
                            objs.Add(output);
                        }
                        break;
                    case Protocol.AgencyOutputs: // 输出AgencyOutput
                        {
                            var curIndex = 4;
                            while (true)
                            {
                                if (binary.Length < curIndex + 2) break;
                                var jsonSize = BitConverter.ToUInt16(binary.AsSpan(curIndex, 2));
                                if (binary.Length < curIndex + 2 + jsonSize) break;
                                var output = new AgencyOutput();
                                output.Proto = JsonSerializer.Deserialize<AgencyOutputProto>(binary.AsSpan(curIndex + 2, jsonSize));
                                var dataSize = 0;
                                if (output.Proto.DataSize > 0)
                                {
                                    if (binary.Length < curIndex + 2 + jsonSize + output.Proto.DataSize) break;
                                    output.OutputData = binary.AsSpan(curIndex + 2 + jsonSize, output.Proto.DataSize).ToArray();
                                    dataSize = output.Proto.DataSize;
                                }
                                else if (output.Proto.DataSize == 0) output.OutputData = [];
                                objs.Add(output);
                                curIndex += 2 + jsonSize + dataSize;
                            }
                        }
                        break;
                    case Protocol.SubscriberResponse: // 输出SubscriberResponse
                        {
                            var jsonSize = BitConverter.ToInt32(binary.AsSpan(4, 4));
                            var output = new SubscriberResponse();
                            output.Proto = JsonSerializer.Deserialize<SubscriberResponseProto>(binary.AsSpan(8, jsonSize));
                            if (output.Proto.DataSizes != null)
                            {
                                output.Data = new byte[output.Proto.DataSizes.Length][];
                                int curIndex = 8 + jsonSize;
                                for (int i = 0; i < output.Data.Length; i++)
                                {
                                    var curSize = output.Proto.DataSizes[i];
                                    if (curSize < 0) continue;
                                    output.Data[i] = new byte[curSize];
                                    if (curSize == 0) continue;
                                    Array.Copy(binary, curIndex, output.Data[i], 0, curSize);
                                    curIndex += curSize;
                                }
                            }
                            objs.Add(output);
                        }
                        break;
                    case Protocol.TaskStart: // 输出TaskStartProto
                        objs.Add(JsonSerializer.Deserialize<TaskStartProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.TaskProgress: // 输出TaskProgressProto
                        objs.Add(JsonSerializer.Deserialize<TaskProgressProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.TaskResult: // 输出TaskResultProto
                        objs.Add(JsonSerializer.Deserialize<TaskResultProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.SessionManualTrigger: // 输出ManualTriggerSample (Timeline未赋值)
                        {
                            var session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(4, 8)));
                            var offset = BitConverter.ToDouble(binary.AsSpan(12, 8));
                            var mtSample = new ManualTriggerSample();
                            mtSample.SetTime(session, offset, TimeOffsetSync.HostArrival, null, 0);
                            for (int i = 0; i < 16; i++) mtSample.States[i] = binary[20 + i] == 1;
                            objs.Add(mtSample);
                        }
                        break;
                    case Protocol.SessionBusMessage: // 输出BusMessageSample (Timeline未赋值)
                        {
                            var session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(4, 8)));
                            var offset = BitConverter.ToDouble(binary.AsSpan(12, 8));
                            var sync = (TimeOffsetSync)binary[24];
                            var msgSample = new BusMessageSample();
                            msgSample.SetTime(session, offset, sync, null, 0);
                            msgSample.ID = BitConverter.ToUInt32(binary.AsSpan(20, 4));
                            msgSample.Channel = binary[25];
                            msgSample.Type = (BusChannelType)binary[26];
                            msgSample.State = (BusRawDataState)binary[27];
                            msgSample.Length = BitConverter.ToUInt16(binary.AsSpan(28, 2));
                            var dataLength = BitConverter.ToUInt16(binary.AsSpan(30, 2));
                            msgSample.Data = dataLength == 0 ? [] : binary.AsSpan(32, dataLength).ToArray();
                            objs.Add(msgSample);
                        }
                        break;
                    case Protocol.SessionBusMessages: // 输出BusMessageSample (Timeline未赋值)
                        {
                            var curIndex = 4;
                            while (true)
                            {
                                if (binary.Length < curIndex + 28) break;
                                var session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(curIndex, 8)));
                                var offset = BitConverter.ToDouble(binary.AsSpan(curIndex + 8, 8));
                                var sync = (TimeOffsetSync)binary[curIndex + 20];
                                var msgSample = new BusMessageSample();
                                msgSample.SetTime(session, offset, sync, null, 0);
                                msgSample.ID = BitConverter.ToUInt32(binary.AsSpan(curIndex + 16, 4));
                                msgSample.Channel = binary[curIndex + 21];
                                msgSample.Type = (BusChannelType)binary[curIndex + 22];
                                msgSample.State = (BusRawDataState)binary[curIndex + 23];
                                msgSample.Length = BitConverter.ToUInt16(binary.AsSpan(curIndex + 24, 2));
                                var dataLength = BitConverter.ToUInt16(binary.AsSpan(curIndex + 26, 2));
                                if (binary.Length < curIndex + 28 + dataLength) break;
                                msgSample.Data = dataLength == 0 ? [] : binary.AsSpan(curIndex + 28, dataLength).ToArray();
                                objs.Add(msgSample);
                                curIndex += 28 + dataLength;
                            }
                        }
                        break;
                    case Protocol.SessionVideoFrame: // 输出JpegSample (Timeline未赋值)
                        {
                            var session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(4, 8)));
                            var offset = BitConverter.ToDouble(binary.AsSpan(12, 8));
                            var sync = (TimeOffsetSync)binary[57];
                            var cpuTick = BitConverter.ToUInt64(binary.AsSpan(24, 8));
                            var hostPosix = BitConverter.ToUInt64(binary.AsSpan(32, 8));
                            var guestPosix = BitConverter.ToUInt64(binary.AsSpan(40, 8));
                            var gnssPosix = BitConverter.ToUInt64(binary.AsSpan(48, 8));
                            IndependentTimeInfo timeInfo = null;
                            if (cpuTick != 0 || hostPosix != 0 || guestPosix != 0 || gnssPosix != 0) timeInfo = new IndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix);
                            var jpegSample = new JpegSample();
                            jpegSample.SetTime(session, offset, sync, timeInfo, 0);
                            jpegSample.Channel = binary[56];
                            var jpegLength = BitConverter.ToUInt32(binary.AsSpan(20, 4));
                            jpegSample.Jpeg = jpegLength == 0 ? [] : binary.AsSpan(58, (int)jpegLength).ToArray();
                            objs.Add(jpegSample);
                        }
                        break;
                    case Protocol.SessionGeneralSample: // 输出GeneralSample (Timeline未赋值)
                        {
                            var session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(8, 8)));
                            var offset = BitConverter.ToDouble(binary.AsSpan(16, 8));
                            var sync = (TimeOffsetSync)binary[64];
                            var cpuTick = BitConverter.ToUInt64(binary.AsSpan(32, 8));
                            var hostPosix = BitConverter.ToUInt64(binary.AsSpan(40, 8));
                            var guestPosix = BitConverter.ToUInt64(binary.AsSpan(48, 8));
                            var gnssPosix = BitConverter.ToUInt64(binary.AsSpan(56, 8));
                            IndependentTimeInfo timeInfo = null;
                            if (cpuTick != 0 || hostPosix != 0 || guestPosix != 0 || gnssPosix != 0) timeInfo = new IndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix);
                            var gSample = new GeneralSample();
                            gSample.SetTime(session, offset, sync, timeInfo, 0);
                            var elemCount = BitConverter.ToUInt32(binary.AsSpan(4, 4));
                            var protocolLength = BitConverter.ToUInt32(binary.AsSpan(24, 4));
                            gSample.NumberOfSignificants = BitConverter.ToInt32(binary.AsSpan(28, 4));
                            gSample.Channel = binary[65] == 1 ? BitConverter.ToUInt16(binary.AsSpan(66, 2)) : null;
                            var elems = new GeneralSampleValue[elemCount];
                            var elemMeta = new byte[elemCount];
                            int doubleCount = 0, intCount = 0, byteCount = 0, textCount = 0;
                            for (int i = 0; i < elemCount; i++)
                            {
                                var meta = binary[72 + i];
                                switch (meta)
                                {
                                    case 1:
                                        byteCount++;
                                        break;
                                    case 2:
                                        intCount++;
                                        break;
                                    case 3:
                                        doubleCount++;
                                        break;
                                    case 4:
                                        textCount++;
                                        break;
                                }
                                elemMeta[i] = meta;
                            }
                            var metaSize = ((uint)elemCount + 7) & 0xfffffff8;
                            int posDouble = 72 + (int)metaSize;
                            int posInt = posDouble + doubleCount * 8;
                            int posTextLength = posInt + intCount * 4;
                            int posByte = posTextLength + textCount * 2;
                            int posProtocol = posByte + byteCount;
                            int posTextUTF8 = posProtocol + (int)protocolLength;
                            gSample.Protocol = Encoding.UTF8.GetString(binary.AsSpan(posProtocol, (int)protocolLength));
                            for (int i = 0; i < elemCount; i++)
                            {
                                switch (elemMeta[i])
                                {
                                    case 1:
                                        elems[i] = new GeneralSampleValue((double)binary[posByte++]);
                                        break;
                                    case 2:
                                        elems[i] = new GeneralSampleValue((double)BitConverter.ToInt32(binary.AsSpan(posInt, 4)));
                                        posInt += 4;
                                        break;
                                    case 3:
                                        elems[i] = new GeneralSampleValue(BitConverter.ToDouble(binary.AsSpan(posDouble, 8)));
                                        posDouble += 8;
                                        break;
                                    case 4:
                                        {
                                            var textLength = BitConverter.ToUInt16(binary.AsSpan(posTextLength, 2));
                                            posTextLength += 2;
                                            elems[i] = new GeneralSampleValue(Encoding.UTF8.GetString(binary.AsSpan(posTextUTF8, textLength)));
                                            posTextUTF8 += textLength;
                                        }
                                        break;
                                }
                            }
                            gSample.Values = elems.ToList();
                            objs.Add(gSample);
                        }
                        break;
                    case Protocol.SessionSignal: // 输出SignalsData
                        {
                            var signalsData = new SignalsData();
                            signalsData.Session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(12, 8)));
                            var idLength = BitConverter.ToUInt32(binary.AsSpan(4, 4));
                            var signalCount = BitConverter.ToUInt32(binary.AsSpan(8, 4));
                            int posDoubles = 20;
                            int posBytes = posDoubles + (int)signalCount * 16;
                            int posID = posBytes + (int)signalCount;
                            signalsData.SignalID = Encoding.UTF8.GetString(binary.AsSpan(posID, (int)idLength));
                            signalsData.Signals = new SignalData[signalCount];
                            for (int i = 0; i < signalCount; i++)
                            {
                                var offset = BitConverter.ToDouble(binary.AsSpan(posDoubles, 8));
                                posDoubles += 8;
                                var val = BitConverter.ToDouble(binary.AsSpan(posDoubles, 8));
                                posDoubles += 8;
                                var sync = (TimeOffsetSync)binary[posBytes++];
                                signalsData.Signals[i] = new SignalData(offset, sync, val);
                            }
                            objs.Add(signalsData);
                        }
                        break;
                    case Protocol.SessionPointCloudSlice: // 输出PointCloudSlice
                        {
                            var slice = new PointCloudSlice();
                            slice.Channel = binary[61];
                            slice.Session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(4, 8)));
                            slice.Offset = BitConverter.ToDouble(binary.AsSpan(12, 8));
                            slice.SourcePointCount = BitConverter.ToInt32(binary.AsSpan(24, 4));
                            slice.Sync = (TimeOffsetSync)binary[60];
                            var cpuTick = BitConverter.ToUInt64(binary.AsSpan(28, 8));
                            var hostPosix = BitConverter.ToUInt64(binary.AsSpan(36, 8));
                            var guestPosix = BitConverter.ToUInt64(binary.AsSpan(44, 8));
                            var gnssPosix = BitConverter.ToUInt64(binary.AsSpan(52, 8));
                            if (cpuTick != 0 || hostPosix != 0 || guestPosix != 0 || gnssPosix != 0) slice.TimeInfo = new IndependentTimeInfo(cpuTick, hostPosix, guestPosix, gnssPosix);
                            slice.WithIntensities = binary[62] == 1;
                            if (binary.Length > 63) slice.Data = binary.AsSpan(63).ToArray();
                            else slice.Data = [];
                            objs.Add(slice);
                        }
                        break;
                    case Protocol.SessionAudioFrame: // 输出AudioFrameSample
                        {
                            var session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(4, 8)));
                            var offset = BitConverter.ToDouble(binary.AsSpan(12, 8));
                            var audioSample = new AudioFrameSample();
                            audioSample.SetTime(session, offset, TimeOffsetSync.HostArrival, null, 0);
                            var sampleCount = BitConverter.ToUInt32(binary.AsSpan(20, 4));
                            var samples = new short[sampleCount];
                            for (int i = 0; i < sampleCount; i++)
                            {
                                samples[i] = BitConverter.ToInt16(binary.AsSpan(24 + 2 * i, 2));
                            }
                            audioSample.Data = samples;
                            objs.Add(audioSample);
                        }
                        break;
                    case Protocol.SessionSecondMosaic: // 输出SecondMosaicPack
                        {
                            var pack = new SecondMosaicPack();
                            pack.Session = SessionIdentifier.FromLongNumber(BitConverter.ToUInt64(binary.AsSpan(8, 8)));
                            pack.Second = BitConverter.ToInt32(binary.AsSpan(4, 4));
                            pack.MosaicHeight = BitConverter.ToUInt16(binary.AsSpan(16, 2));
                            pack.Channel = BitConverter.ToUInt16(binary.AsSpan(18, 2));
                            var sampleCount = BitConverter.ToUInt16(binary.AsSpan(20, 2));
                            pack.VgaHeight = BitConverter.ToUInt16(binary.AsSpan(22, 2));
                            if (pack.VgaHeight != 0)
                            {
                                pack.Offsets = new double[sampleCount];
                                pack.Mosaics = new CommonImage[sampleCount];
                                int posOffsets = 24;
                                int posMosaics = posOffsets + 8 * (int)sampleCount;
                                for (int i = 0; i < sampleCount; i++)
                                {
                                    pack.Offsets[i] = BitConverter.ToDouble(binary.AsSpan(posOffsets, 8));
                                    posOffsets += 8;
                                    var image = CommonImage.Create(MosaicWidth, pack.MosaicHeight, false);
                                    var imageData = image.Data;
                                    for (int v = 0; v < pack.MosaicHeight; v++)
                                    {
                                        var rowIndex = v * image.RowBytes;
                                        for (int u = 0; u < MosaicWidth; u++)
                                        {
                                            var cellIndex = rowIndex + u * 3;
                                            var pixel = BitConverter.ToUInt16(binary.AsSpan(posMosaics, 2));
                                            posMosaics += 2;
                                            imageData[cellIndex] = (byte)((pixel & 0x001f) << 3);
                                            imageData[cellIndex + 1] = (byte)((pixel & 0x07e0) >> 3);
                                            imageData[cellIndex + 2] = (byte)((pixel & 0xf800) >> 8);
                                        }
                                    }
                                    pack.Mosaics[i] = image;
                                }
                                objs.Add(pack);
                            }
                        }
                        break;
                    case Protocol.SecondProfile: // 输出SecondProfileProto
                        objs.Add(JsonSerializer.Deserialize<SecondProfileProto>(binary.AsSpan(4)));
                        break;
                    case Protocol.RoundTripTest: // 不输出，直接发送响应包
                        socket.Send(binary, false);
                        break;
                }
            }
            return objs.ToArray();
        }

        public async void Prompt(String text)
        {
            if (text == null) return;
            if (text == "clear") return;

            if (text == "exit" || text == "quit")
            {
                await Disconnect();
                return;
            }

            if (!await sendProtoObject(Protocol.Prompt, new PromptProto{ Text = text }, false)) await socket.Close();
        }

        public async void Abort()
        {
            if (!await sendProtocolOnly(Protocol.Abort, false)) await socket.Close();
        }

        public void StartSendingLoadedFileData(byte[] data, String filePath)
        {
            var dataLength = data != null ? data.Length : 0;
            var json = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new LoadedFileDataProto
            {
                Loaded = data != null,
                DataSize = dataLength,
                FilePath = filePath,
            }));

            var binary = new byte[8 + json.Length + dataLength];
            BitConverter.TryWriteBytes(binary.AsSpan(0, 4), (int)Protocol.LoadedFileData);
            BitConverter.TryWriteBytes(binary.AsSpan(4, 4), json.Length);
            Array.Copy(json, 0, binary, 8, json.Length);
            if (dataLength > 0) Array.Copy(data, 0, binary, 8 + json.Length, dataLength);

            var packets = RemoteSegment.Generate(binary, Protocol.LoadedFileData);
            foreach (var packet in packets) packetsToSend.Enqueue(packet);

            if (sendFileDataTask == null)
            {
                shouldEnd = false;
                sendFileDataTask = loopSendLoadedFileData();
            }
        }

        private async Task loopSendLoadedFileData()
        {
            while (!shouldEnd)
            {
                await Task.Delay(1);

                byte[] packet = null;
                if (!packetsToSend.TryDequeue(out packet)) break;

                if (!await socket.Send(packet, false)) break;
            }

            sendFileDataTask = null;
        }

        public async void SendSaveFileDataResult(bool saved, String filePath)
        {
            var json = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(new SaveFileDataResultProto
            {
                Saved = saved,
                FilePath = filePath,
            }));

            var binary = new byte[8 + json.Length];
            BitConverter.TryWriteBytes(binary.AsSpan(0, 4), (int)Protocol.SaveFileDataResult);
            BitConverter.TryWriteBytes(binary.AsSpan(4, 4), json.Length);
            Array.Copy(json, 0, binary, 8, json.Length);
            if (!await socket.Send(binary, false)) await socket.Close();
        }

        public async void SendSubscriberRequest(String guid, bool closeRequest, bool checkOnly)
        {
            if (!await sendProtoObject(Protocol.SubscriberRequest, new SubscriberRequestProto
            {
                GUID = guid,
                CloseRequest = closeRequest,
                CheckOnly = checkOnly,
            }, false)) await socket.Close();
        }

        public async void SendTaskCancellation(String guid)
        {
            if (!await sendProtoObject(Protocol.CancelTask, new CancelTaskProto
            {
                GUID = guid,
            }, false)) await socket.Close();
        }

        public async void SendSetVideoTransfer(int channel, bool enable)
        {
            if (!await sendProtoObject(Protocol.SetVideoTransfer, new SetVideoTransferProto{ Channel = channel, Enable = enable }, false)) await socket.Close();
        }

        public async void SendRoundTripTime(int time)
        {
            if (!await sendProtoObject(Protocol.RoundTripTime, new RoundTripTimeProto{ Milliseconds = time }, false)) await socket.Close();
        }

        private Task<bool> sendProtoObject<ProtoType>(Protocol protocol, ProtoType obj, bool optional)
        {
            var body = Encoding.UTF8.GetBytes(JsonSerializer.Serialize<ProtoType>(obj));
            var binary = new byte[4 + body.Length];
            BitConverter.TryWriteBytes(binary.AsSpan(0, 4), (int)protocol);
            Array.Copy(body, 0, binary, 4, body.Length);
            return socket.Send(binary, optional);
        }

        private Task<bool> sendProtocolOnly(Protocol protocol, bool optional)
        {
            var binary = new byte[4];
            BitConverter.TryWriteBytes(binary.AsSpan(0, 4), (int)protocol);
            return socket.Send(binary, optional);
        }

        private RemoteSegmentRestore segmentRestore = new RemoteSegmentRestore();
        private ConcurrentQueue<byte[]> packetsToSend = new ConcurrentQueue<byte[]>();
        private Task sendFileDataTask = null;
        private bool shouldEnd = false;

        private const int MosaicWidth = 8;
    }

    class SaveFileDataRequest
    {
        public SaveFileDataProto Proto { get; set; }
        public byte[] SaveFileData { get; set; }
    }

    class ReceiveBinary
    {
        public byte[] Data { get; set; }
    }

    class ReceivedManualTrigger : ReceiveBinary
    {
        public ulong HostPosix { get; set; } // 纳秒
        public bool[] States { get; set; }
    }

    class ReceivedBusMessage : ReceiveBinary
    {
        public ulong HostPosix { get; set; } // 纳秒
        public int Channel { get; set; } // 1~48
        public uint ID { get; set; }
        public byte[] MessageData { get; set; }
        public int ActualLength { get; set; }
        public BusChannelType Type { get; set; }
        public BusRawDataState State { get; set; }
    }

    class ReceivedVideoFrame : ReceiveBinary
    {
        public ulong HostPosix { get; set; } // 纳秒
        public int Channel { get; set; } // 0~23
        public byte[] Jpeg { get; set; }
    }

    class ReceivedAudioFrame
    {
        public ulong HostPosix { get; set; } // 纳秒
        public short[] Samples { get; set; }
    }

    class ReceivedSignals
    {
        public ulong[] HostPosix { get; set; } // 纳秒
        public String SignalID { get; set; }
        public double[] Values { get; set; }
    }

    class ReceivedGeneralSample
    {
        public ulong HostPosix { get; set; } // 纳秒
        public string Protocol { get; set; }
        public int? Channel { get; set; }
        public GeneralSampleValue[] Elements { get; set; }
    }

    class ReceivedPointCloud
    {
        public ulong HostPosix { get; set; } // 纳秒
        public int Channel { get; set; }
        public int PointCount { get; set; }
        public short[,] Points { get; set; } // cm
        public ushort[] Intensities { get; set; } // 0~10000
    }

    class AgencyOutput
    {
        public AgencyOutputProto Proto { get; set; }
        public byte[] OutputData { get; set; }
    }

    class SubscriberResponse
    {
        public SubscriberResponseProto Proto { get; set; }
        public byte[][] Data { get; set; }
    }

    class JpegSample : Sample
    {
        public int Channel { get; set; } // 0~23
        public byte[] Jpeg { get; set; }
    }

    class SecondMosaicPack
    {
        public int Channel { get; set; }
        public SessionIdentifier Session { get; set; }
        public int Second { get; set; }
        public int MosaicHeight { get; set; }
        public int VgaHeight { get; set; }
        public double[] Offsets { get; set; }
        public CommonImage[] Mosaics { get; set; }
    }

    class PointCloudSlice
    {
        public int Channel { get; set; }
        public SessionIdentifier Session { get; set; }
        public double Offset { get; set; }
        public int SourcePointCount { get; set; }
        public TimeOffsetSync Sync { get; set; }
        public IndependentTimeInfo TimeInfo { get; set; }
        public bool WithIntensities { get; set; }
        public byte[] Data { get; set; }
    }
}